﻿using System;
using System.Collections.Generic;
using MugenInjection.Bindings.Builders;
using MugenInjection.Infrastructure;
using MugenInjection.Interface;
using MugenInjection.Interface.Components;
using MugenInjection.Syntax;
using MugenInjection.Syntax.Type;

namespace MugenInjection.Core
{
    /// <summary>
    ///     Represents a base class for module which can load in <see cref="IInjector" />.
    /// </summary>
    public abstract class InjectorModule : IInjectorModule
    {
        #region Fields

        private readonly List<IComponent> _components = new List<IComponent>();
        private readonly List<IBindingBuilder> _bindingBuilders = new List<IBindingBuilder>();
        private readonly Dictionary<IInjector, List<IBinding>> _bindings = new Dictionary<IInjector, List<IBinding>>();
        private readonly Dictionary<IInjector, List<IComponent>> _componentDict = new Dictionary<IInjector, List<IComponent>>();

        private readonly object _locker = new object();
        private IInjector _injector;

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="InjectorModule"/> class.
        /// </summary>
        protected InjectorModule()
        {
        }

        #endregion

        #region Properties

        /// <summary>
        ///  Gets the current <see cref="IInjector" />.
        /// </summary>
        protected IInjector Injector
        {
            get
            {
                Validate.EnsureIsInitializedValue(_injector, "Injector");
                return _injector;
            }
            set { _injector = value; }
        }

        #endregion

        #region Methods

        #region Bindings

        /// <summary>
        ///     Declares a binding for the specified service.
        /// </summary>
        /// <param name="services">The specified service types.</param>
        /// <returns>Fluent syntax.</returns>
        protected IBindingSyntax Bind(params Type[] services)
        {
            Validate.ArgumentNotNull(services, "services");
            var bindingBuilder = new BindingBuilder<object, object, object, object>(services, Injector);
            lock (_locker)
                _bindingBuilders.Add(bindingBuilder);
            return bindingBuilder;
        }

        /// <summary>
        ///     Declares a binding for the specified service <see cref="T" />.
        /// </summary>
        /// <typeparam name="T">The specified service type.</typeparam>
        /// <returns>Fluent syntax.</returns>
        protected IGenericBindingSyntax<T> Bind<T>()
        {
            var bindingBuilder = new BindingBuilder<T, object, object, object>(new[] { typeof(T) }, Injector);
            lock (_locker)
                _bindingBuilders.Add(bindingBuilder);
            return bindingBuilder;
        }

        /// <summary>
        ///     Declares a binding for the specified services.
        /// </summary>
        /// <typeparam name="T1">The specified service type.</typeparam>
        /// <typeparam name="T2">The specified service type.</typeparam>
        /// <returns>Fluent syntax.</returns>
        protected IGenericBindingSyntax<T1, T2> Bind<T1, T2>()
        {
            var bindingBuilder = new BindingBuilder<T1, T2, object, object>(new[] { typeof(T1), typeof(T2) }, Injector);
            lock (_locker)
                _bindingBuilders.Add(bindingBuilder);
            return bindingBuilder;
        }

        /// <summary>
        ///     Declares a binding for the specified services.
        /// </summary>
        /// <typeparam name="T1">The specified service type.</typeparam>
        /// <typeparam name="T2">The specified service type.</typeparam>
        /// <typeparam name="T3">The specified service type.</typeparam>
        /// <returns>Fluent syntax.</returns>
        protected IGenericBindingSyntax<T1, T2, T3> Bind<T1, T2, T3>()
        {
            var bindingBuilder = new BindingBuilder<T1, T2, T3, object>(new[] { typeof(T1), typeof(T2), typeof(T3) },
                                                                        Injector);
            lock (_locker)
                _bindingBuilders.Add(bindingBuilder);
            return bindingBuilder;
        }

        /// <summary>
        ///     Declares a binding for the specified services.
        /// </summary>
        /// <typeparam name="T1">The specified service type.</typeparam>
        /// <typeparam name="T2">The specified service type.</typeparam>
        /// <typeparam name="T3">The specified service type.</typeparam>
        /// <typeparam name="T4">The specified service type.</typeparam>
        /// <returns>Fluent syntax.</returns>
        protected IGenericBindingSyntax<T1, T2, T3, T4> Bind<T1, T2, T3, T4>()
        {
            var bindingBuilder =
                new BindingBuilder<T1, T2, T3, T4>(new[] { typeof(T1), typeof(T2), typeof(T3), typeof(T4) }, Injector);
            lock (_locker)
                _bindingBuilders.Add(bindingBuilder);
            return bindingBuilder;
        }

        /// <summary>
        ///     Declares a binding for the specified service <see cref="T" />.
        /// </summary>
        /// <typeparam name="T">The specified service type.</typeparam>
        /// <typeparam name="TTypeTo">Type to bind specified service. </typeparam>
        /// <returns>Fluent syntax.</returns>
        protected ITypeCallbackConstructorObjectPriorityScopeUseWhenWithSyntax<TTypeTo> BindTo<T, TTypeTo>()
            where TTypeTo : T
        {
            IGenericBindingSyntax<T> bindingBuilder = new BindingBuilder<T, object, object, object>(new[] { typeof(T) },
                                                                                                    Injector);
            lock (_locker)
                _bindingBuilders.Add(bindingBuilder);
            return bindingBuilder.To<TTypeTo>();
        }

        #endregion

        #region Components

        /// <summary>
        ///     Adds the specified <see cref="IComponent" />.
        /// </summary>
        /// <param name="component">
        ///     The specified <see cref="IComponent" /> for adding.
        /// </param>
        protected void AddComponent(IComponent component)
        {
            Validate.ArgumentNotNull(component, "component");
            lock (_locker)
                _components.Add(component);
        }

        #endregion

        /// <summary>
        ///     Loads bindings and components in current module.
        /// </summary>
        public abstract void Load();

        /// <summary>
        ///     Unloads bindings and components in current module.
        /// </summary>
        protected virtual void Unload(IList<IBinding> loadedBinding, IList<IComponent> loadedComponent)
        {
            for (int index = 0; index < loadedBinding.Count; index++)
            {
                IBinding binding = loadedBinding[index];
                Injector.RemoveBinding(binding);
            }
            for (int index = 0; index < loadedComponent.Count; index++)
            {
                IComponent component = loadedComponent[index];
                Injector.Components.Remove(component);
            }
        }

        #endregion

        #region Implementation of IInjectorModule

        /// <summary>
        ///     Loads bindings and components in current module.
        /// </summary>
        /// <param name="injector">The specified <see cref="IInjector"/>.</param>
        public void Load(IInjector injector)
        {
            Validate.ArgumentNotNull(injector, "injector");
            lock (_locker)
            {
                Injector = injector;
                Load();

                //Load bindings
                var bindings = new List<IBinding>();
                for (int index = 0; index < _bindingBuilders.Count; index++)
                {
                    IBinding binding = _bindingBuilders[index].Build();
                    bindings.Add(binding);
                    injector.AddBinding(binding);
                }
                _bindingBuilders.Clear();
                _bindings.Add(_injector, bindings);

                //Load components
                var components = new List<IComponent>(_components);
                foreach (IComponent component in components)
                    injector.Components.Add(component);
                _components.Clear();
                _componentDict.Add(injector, components);
                Injector = null;
            }
        }

        /// <summary>
        ///     Unloads bindings and components in current module.
        /// </summary>
        public void Unload(IInjector injector)
        {
            Validate.ArgumentNotNull(injector, "injector");
            lock (_locker)
            {
                Injector = injector;
                List<IComponent> components = _componentDict[injector];
                List<IBinding> bindings = _bindings[injector];
                Unload(bindings, components);
                _componentDict.Remove(injector);
                _bindings.Remove(injector);
                Injector = null;
            }
        }

        /// <summary>
        /// Gets the module name.
        /// </summary>
        public virtual string Name
        {
            get { return GetType().FullName; }
        }

        #endregion
    }
}